/*******************************************************************************
 jquery.mb.components
 Copyright (c) 2001-2010. Matteo Bicocchi (Pupunzi); Open lab srl, Firenze - Italy
 email: info@pupunzi.com
 site: http://pupunzi.com

 Licences: MIT, GPL
 http://www.opensource.org/licenses/mit-license.php
 http://www.gnu.org/licenses/gpl.html
 ******************************************************************************/

(function () {


    document.exifData = true;

    var BinaryFile = function (strData, iDataOffset, iDataLength) {
        var data = strData;
        var dataOffset = iDataOffset || 0;
        var dataLength = 0;

        this.getRawData = function () {
            return data;
        }

        if (typeof strData == "string") {
            dataLength = iDataLength || data.length;

            this.getByteAt = function (iOffset) {
                return data.charCodeAt(iOffset + dataOffset) & 0xFF;
            }
        } else if (typeof strData == "unknown") {
            dataLength = iDataLength || IEBinary_getLength(data);

            this.getByteAt = function (iOffset) {
                return IEBinary_getByteAt(data, iOffset + dataOffset);
            }
        }

        this.getLength = function () {
            return dataLength;
        }

        this.getSByteAt = function (iOffset) {
            var iByte = this.getByteAt(iOffset);
            if (iByte > 127)
                return iByte - 256;
            else
                return iByte;
        }

        this.getShortAt = function (iOffset, bBigEndian) {
            var iShort = bBigEndian ?
                (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1)
                : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset)
            if (iShort < 0) iShort += 65536;
            return iShort;
        }
        this.getSShortAt = function (iOffset, bBigEndian) {
            var iUShort = this.getShortAt(iOffset, bBigEndian);
            if (iUShort > 32767)
                return iUShort - 65536;
            else
                return iUShort;
        }
        this.getLongAt = function (iOffset, bBigEndian) {
            var iByte1 = this.getByteAt(iOffset),
                iByte2 = this.getByteAt(iOffset + 1),
                iByte3 = this.getByteAt(iOffset + 2),
                iByte4 = this.getByteAt(iOffset + 3);

            var iLong = bBigEndian ?
                (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
                : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
            if (iLong < 0) iLong += 4294967296;
            return iLong;
        }
        this.getSLongAt = function (iOffset, bBigEndian) {
            var iULong = this.getLongAt(iOffset, bBigEndian);
            if (iULong > 2147483647)
                return iULong - 4294967296;
            else
                return iULong;
        }
        this.getStringAt = function (iOffset, iLength) {
            var aStr = [];
            for (var i = iOffset, j = 0; i < iOffset + iLength; i++, j++) {
                aStr[j] = String.fromCharCode(this.getByteAt(i));
            }
            return aStr.join("");
        }

        this.getCharAt = function (iOffset) {
            return String.fromCharCode(this.getByteAt(iOffset));
        }
        this.toBase64 = function () {
            return window.btoa(data);
        }
        this.fromBase64 = function (strBase64) {
            data = window.atob(strBase64);
        }
    }


    var BinaryAjax = (function () {

        function createRequest() {
            var oHTTP = null;
            if (window.XMLHttpRequest) {
                oHTTP = new XMLHttpRequest();
            } else if (window.ActiveXObject) {
                oHTTP = new ActiveXObject("Microsoft.XMLHTTP");
            }
            return oHTTP;
        }

        function getHead(strURL, fncCallback, fncError) {
            var oHTTP = createRequest();
            if (oHTTP) {
                if (fncCallback) {
                    if (typeof(oHTTP.onload) != "undefined") {
                        oHTTP.onload = function () {
                            if (oHTTP.status == "200") {
                                fncCallback(this);
                            } else {
                                if (fncError) fncError();
                            }
                            oHTTP = null;
                        };
                    } else {
                        oHTTP.onreadystatechange = function () {
                            if (oHTTP.readyState == 4) {
                                if (oHTTP.status == "200") {
                                    fncCallback(this);
                                } else {
                                    if (fncError) fncError();
                                }
                                oHTTP = null;
                            }
                        };
                    }
                }
                oHTTP.open("HEAD", strURL, true);
                oHTTP.send(null);
            } else {
                if (fncError) fncError();
            }
        }

        function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize) {
            var oHTTP = createRequest();
            if (oHTTP) {

                var iDataOffset = 0;
                if (aRange && !bAcceptRanges) {
                    iDataOffset = aRange[0];
                }
                var iDataLen = 0;
                if (aRange) {
                    iDataLen = aRange[1] - aRange[0] + 1;
                }

                if (fncCallback) {
                    if (typeof(oHTTP.onload) != "undefined") {
                        oHTTP.onload = function () {

                            if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") {
                                this.binaryResponse = new BinaryFile(this.responseText, iDataOffset, iDataLen);
                                this.fileSize = iFileSize || this.getResponseHeader("Content-Length");
                                fncCallback(this);
                            } else {
                                if (fncError) fncError();
                            }
                            oHTTP = null;
                        };
                    } else {
                        oHTTP.onreadystatechange = function () {
                            if (oHTTP.readyState == 4) {
                                if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") {
                                    this.binaryResponse = new BinaryFile(oHTTP.responseBody, iDataOffset, iDataLen);
                                    this.fileSize = iFileSize || this.getResponseHeader("Content-Length");
                                    fncCallback(this);
                                } else {
                                    if (fncError) fncError();
                                }
                                oHTTP = null;
                            }
                        };
                    }
                }
                oHTTP.open("GET", strURL, true);

                if (oHTTP.overrideMimeType) oHTTP.overrideMimeType('text/plain; charset=x-user-defined');

                if (aRange && bAcceptRanges) {
                    oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]);
                }

                oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT");

                oHTTP.send(null);
            } else {
                if (fncError) fncError();
            }
        }

        return function (strURL, fncCallback, fncError, aRange) {

            if (aRange) {
                getHead(
                    strURL,
                    function (oHTTP) {
                        var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"), 10);
                        var strAcceptRanges = oHTTP.getResponseHeader("Accept-Ranges");

                        var iStart, iEnd;
                        iStart = aRange[0];
                        if (aRange[0] < 0)
                            iStart += iLength;
                        iEnd = iStart + aRange[1] - 1;

                        sendRequest(strURL, fncCallback, fncError, [iStart, iEnd], (strAcceptRanges == "bytes"), iLength);
                    }
                );

            } else {
                sendRequest(strURL, fncCallback, fncError);
            }
        }

    }());


    document.write(
        "<script type='text/vbscript'>\r\n"
            + "Function IEBinary_getByteAt(strBinary, iOffset)\r\n"
            + "	IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n"
            + "End Function\r\n"
            + "Function IEBinary_getLength(strBinary)\r\n"
            + "	IEBinary_getLength = LenB(strBinary)\r\n"
            + "End Function\r\n"
            + "</script>\r\n"
    );


    var EXIF = {};

    (function () {

        var bDebug = false;

        EXIF.Tags = {

            // version tags
            0x9000:"ExifVersion", // EXIF version
            0xA000:"FlashpixVersion", // Flashpix format version

            // colorspace tags
            0xA001:"ColorSpace", // Color space information tag

            // image configuration
            0xA002:"PixelXDimension", // Valid width of meaningful image
            0xA003:"PixelYDimension", // Valid height of meaningful image
            0x9101:"ComponentsConfiguration", // Information about channels
            0x9102:"CompressedBitsPerPixel", // Compressed bits per pixel

            // user information
            0x927C:"MakerNote", // Any desired information written by the manufacturer
            0x9286:"UserComment", // Comments by user

            // related file
            0xA004:"RelatedSoundFile", // Name of related sound file

            // date and time
            0x9003:"DateTimeOriginal", // Date and time when the original image was generated
            0x9004:"DateTimeDigitized", // Date and time when the image was stored digitally
            0x9290:"SubsecTime", // Fractions of seconds for DateTime
            0x9291:"SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
            0x9292:"SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized

            // picture-taking conditions
            0x829A:"ExposureTime", // Exposure time (in seconds)
            0x829D:"FNumber", // F number
            0x8822:"ExposureProgram", // Exposure program
            0x8824:"SpectralSensitivity", // Spectral sensitivity
            0x8827:"ISOSpeedRatings", // ISO speed rating
            0x8828:"OECF", // Optoelectric conversion factor
            0x9201:"ShutterSpeedValue", // Shutter speed
            0x9202:"ApertureValue", // Lens aperture
            0x9203:"BrightnessValue", // Value of brightness
            0x9204:"ExposureBias", // Exposure bias
            0x9205:"MaxApertureValue", // Smallest F number of lens
            0x9206:"SubjectDistance", // Distance to subject in meters
            0x9207:"MeteringMode", // Metering mode
            0x9208:"LightSource", // Kind of light source
            0x9209:"Flash", // Flash status
            0x9214:"SubjectArea", // Location and area of main subject
            0x920A:"FocalLength", // Focal length of the lens in mm
            0xA20B:"FlashEnergy", // Strobe energy in BCPS
            0xA20C:"SpatialFrequencyResponse", //
            0xA20E:"FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
            0xA20F:"FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
            0xA210:"FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
            0xA214:"SubjectLocation", // Location of subject in image
            0xA215:"ExposureIndex", // Exposure index selected on camera
            0xA217:"SensingMethod", // Image sensor type
            0xA300:"FileSource", // Image source (3 == DSC)
            0xA301:"SceneType", // Scene type (1 == directly photographed)
            0xA302:"CFAPattern", // Color filter array geometric pattern
            0xA401:"CustomRendered", // Special processing
            0xA402:"ExposureMode", // Exposure mode
            0xA403:"WhiteBalance", // 1 = auto white balance, 2 = manual
            0xA404:"DigitalZoomRation", // Digital zoom ratio
            0xA405:"FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
            0xA406:"SceneCaptureType", // Type of scene
            0xA407:"GainControl", // Degree of overall image gain adjustment
            0xA408:"Contrast", // Direction of contrast processing applied by camera
            0xA409:"Saturation", // Direction of saturation processing applied by camera
            0xA40A:"Sharpness", // Direction of sharpness processing applied by camera
            0xA40B:"DeviceSettingDescription", //
            0xA40C:"SubjectDistanceRange", // Distance to subject

            // other tags
            0xA005:"InteroperabilityIFDPointer",
            0xA420:"ImageUniqueID"        // Identifier assigned uniquely to each image
        };

        EXIF.TiffTags = {
            0x0100:"ImageWidth",
            0x0101:"ImageHeight",
            0x8769:"ExifIFDPointer",
            0x8825:"GPSInfoIFDPointer",
            0xA005:"InteroperabilityIFDPointer",
            0x0102:"BitsPerSample",
            0x0103:"Compression",
            0x0106:"PhotometricInterpretation",
            0x0112:"Orientation",
            0x0115:"SamplesPerPixel",
            0x011C:"PlanarConfiguration",
            0x0212:"YCbCrSubSampling",
            0x0213:"YCbCrPositioning",
            0x011A:"XResolution",
            0x011B:"YResolution",
            0x0128:"ResolutionUnit",
            0x0111:"StripOffsets",
            0x0116:"RowsPerStrip",
            0x0117:"StripByteCounts",
            0x0201:"JPEGInterchangeFormat",
            0x0202:"JPEGInterchangeFormatLength",
            0x012D:"TransferFunction",
            0x013E:"WhitePoint",
            0x013F:"PrimaryChromaticities",
            0x0211:"YCbCrCoefficients",
            0x0214:"ReferenceBlackWhite",
            0x0132:"DateTime",
            0x010E:"ImageDescription",
            0x010F:"Make",
            0x0110:"Model",
            0x0131:"Software",
            0x013B:"Artist",
            0x8298:"Copyright"
        }

        EXIF.GPSTags = {
            0x0000:"GPSVersionID",
            0x0001:"GPSLatitudeRef",
            0x0002:"GPSLatitude",
            0x0003:"GPSLongitudeRef",
            0x0004:"GPSLongitude",
            0x0005:"GPSAltitudeRef",
            0x0006:"GPSAltitude",
            0x0007:"GPSTimeStamp",
            0x0008:"GPSSatellites",
            0x0009:"GPSStatus",
            0x000A:"GPSMeasureMode",
            0x000B:"GPSDOP",
            0x000C:"GPSSpeedRef",
            0x000D:"GPSSpeed",
            0x000E:"GPSTrackRef",
            0x000F:"GPSTrack",
            0x0010:"GPSImgDirectionRef",
            0x0011:"GPSImgDirection",
            0x0012:"GPSMapDatum",
            0x0013:"GPSDestLatitudeRef",
            0x0014:"GPSDestLatitude",
            0x0015:"GPSDestLongitudeRef",
            0x0016:"GPSDestLongitude",
            0x0017:"GPSDestBearingRef",
            0x0018:"GPSDestBearing",
            0x0019:"GPSDestDistanceRef",
            0x001A:"GPSDestDistance",
            0x001B:"GPSProcessingMethod",
            0x001C:"GPSAreaInformation",
            0x001D:"GPSDateStamp",
            0x001E:"GPSDifferential"
        }

        EXIF.StringValues = {
            ExposureProgram:{
                0:"Not defined",
                1:"Manual",
                2:"Normal program",
                3:"Aperture priority",
                4:"Shutter priority",
                5:"Creative program",
                6:"Action program",
                7:"Portrait mode",
                8:"Landscape mode"
            },
            MeteringMode:{
                0:"Unknown",
                1:"Average",
                2:"CenterWeightedAverage",
                3:"Spot",
                4:"MultiSpot",
                5:"Pattern",
                6:"Partial",
                255:"Other"
            },
            LightSource:{
                0:"Unknown",
                1:"Daylight",
                2:"Fluorescent",
                3:"Tungsten (incandescent light)",
                4:"Flash",
                9:"Fine weather",
                10:"Cloudy weather",
                11:"Shade",
                12:"Daylight fluorescent (D 5700 - 7100K)",
                13:"Day white fluorescent (N 4600 - 5400K)",
                14:"Cool white fluorescent (W 3900 - 4500K)",
                15:"White fluorescent (WW 3200 - 3700K)",
                17:"Standard light A",
                18:"Standard light B",
                19:"Standard light C",
                20:"D55",
                21:"D65",
                22:"D75",
                23:"D50",
                24:"ISO studio tungsten",
                255:"Other"
            },
            Flash:{
                0x0000:"Flash did not fire",
                0x0001:"Flash fired",
                0x0005:"Strobe return light not detected",
                0x0007:"Strobe return light detected",
                0x0009:"Flash fired, compulsory flash mode",
                0x000D:"Flash fired, compulsory flash mode, return light not detected",
                0x000F:"Flash fired, compulsory flash mode, return light detected",
                0x0010:"Flash did not fire, compulsory flash mode",
                0x0018:"Flash did not fire, auto mode",
                0x0019:"Flash fired, auto mode",
                0x001D:"Flash fired, auto mode, return light not detected",
                0x001F:"Flash fired, auto mode, return light detected",
                0x0020:"No flash function",
                0x0041:"Flash fired, red-eye reduction mode",
                0x0045:"Flash fired, red-eye reduction mode, return light not detected",
                0x0047:"Flash fired, red-eye reduction mode, return light detected",
                0x0049:"Flash fired, compulsory flash mode, red-eye reduction mode",
                0x004D:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
                0x004F:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
                0x0059:"Flash fired, auto mode, red-eye reduction mode",
                0x005D:"Flash fired, auto mode, return light not detected, red-eye reduction mode",
                0x005F:"Flash fired, auto mode, return light detected, red-eye reduction mode"
            },
            SensingMethod:{
                1:"Not defined",
                2:"One-chip color area sensor",
                3:"Two-chip color area sensor",
                4:"Three-chip color area sensor",
                5:"Color sequential area sensor",
                7:"Trilinear sensor",
                8:"Color sequential linear sensor"
            },
            SceneCaptureType:{
                0:"Standard",
                1:"Landscape",
                2:"Portrait",
                3:"Night scene"
            },
            SceneType:{
                1:"Directly photographed"
            },
            CustomRendered:{
                0:"Normal process",
                1:"Custom process"
            },
            WhiteBalance:{
                0:"Auto white balance",
                1:"Manual white balance"
            },
            GainControl:{
                0:"None",
                1:"Low gain up",
                2:"High gain up",
                3:"Low gain down",
                4:"High gain down"
            },
            Contrast:{
                0:"Normal",
                1:"Soft",
                2:"Hard"
            },
            Saturation:{
                0:"Normal",
                1:"Low saturation",
                2:"High saturation"
            },
            Sharpness:{
                0:"Normal",
                1:"Soft",
                2:"Hard"
            },
            SubjectDistanceRange:{
                0:"Unknown",
                1:"Macro",
                2:"Close view",
                3:"Distant view"
            },
            FileSource:{
                3:"DSC"
            },

            Components:{
                0:"",
                1:"Y",
                2:"Cb",
                3:"Cr",
                4:"R",
                5:"G",
                6:"B"
            }
        }

        function addEvent(oElement, strEvent, fncHandler) {
            if (oElement.addEventListener) {
                oElement.addEventListener(strEvent, fncHandler, false);
            } else if (oElement.attachEvent) {
                oElement.attachEvent("on" + strEvent, fncHandler);
            }
        }


        function imageHasData(oImg) {
            return !!(oImg.exifdata);
        }

        function getImageData(oImg, fncCallback) {
            BinaryAjax(
                oImg.src,
                function (oHTTP) {
                    var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse);
                    oImg.exifdata = oEXIF || {};
                    if (fncCallback) fncCallback();
                }
            )
        }

        function findEXIFinJPEG(oFile) {
            var aMarkers = [];

            if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) {
                return false; // not a valid jpeg
            }

            var iOffset = 2;
            var iLength = oFile.getLength();
            while (iOffset < iLength) {
                if (oFile.getByteAt(iOffset) != 0xFF) {
                    if (bDebug) console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset));
                    return false; // not a valid marker, something is wrong
                }

                var iMarker = oFile.getByteAt(iOffset + 1);

                // we could implement handling for other markers here,
                // but we're only looking for 0xFFE1 for EXIF data

                if (iMarker == 22400) {
                    if (bDebug) console.log("Found 0xFFE1 marker");
                    return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2);
                    iOffset += 2 + oFile.getShortAt(iOffset + 2, true);

                } else if (iMarker == 225) {
                    // 0xE1 = Application-specific 1 (for EXIF)
                    if (bDebug) console.log("Found 0xFFE1 marker");
                    return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2);

                } else {
                    iOffset += 2 + oFile.getShortAt(iOffset + 2, true);
                }

            }

        }


        function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd) {
            var iEntries = oFile.getShortAt(iDirStart, bBigEnd);
            var oTags = {};
            for (var i = 0; i < iEntries; i++) {
                var iEntryOffset = iDirStart + i * 12 + 2;
                var strTag = oStrings[oFile.getShortAt(iEntryOffset, bBigEnd)];
                if (!strTag && bDebug) console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd));
                oTags[strTag] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd);
            }
            return oTags;
        }


        function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd) {
            var iType = oFile.getShortAt(iEntryOffset + 2, bBigEnd);
            var iNumValues = oFile.getLongAt(iEntryOffset + 4, bBigEnd);
            var iValueOffset = oFile.getLongAt(iEntryOffset + 8, bBigEnd) + iTIFFStart;

            switch (iType) {
                case 1: // byte, 8-bit unsigned int
                case 7: // undefined, 8-bit byte, value depending on field
                    if (iNumValues == 1) {
                        return oFile.getByteAt(iEntryOffset + 8, bBigEnd);
                    } else {
                        var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
                        var aVals = [];
                        for (var n = 0; n < iNumValues; n++) {
                            aVals[n] = oFile.getByteAt(iValOffset + n);
                        }
                        return aVals;
                    }
                    break;

                case 2: // ascii, 8-bit byte
                    var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
                    return oFile.getStringAt(iStringOffset, iNumValues - 1);
                    break;

                case 3: // short, 16 bit int
                    if (iNumValues == 1) {
                        return oFile.getShortAt(iEntryOffset + 8, bBigEnd);
                    } else {
                        var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);
                        var aVals = [];
                        for (var n = 0; n < iNumValues; n++) {
                            aVals[n] = oFile.getShortAt(iValOffset + 2 * n, bBigEnd);
                        }
                        return aVals;
                    }
                    break;

                case 4: // long, 32 bit int
                    if (iNumValues == 1) {
                        return oFile.getLongAt(iEntryOffset + 8, bBigEnd);
                    } else {
                        var aVals = [];
                        for (var n = 0; n < iNumValues; n++) {
                            aVals[n] = oFile.getLongAt(iValueOffset + 4 * n, bBigEnd);
                        }
                        return aVals;
                    }
                    break;
                case 5:    // rational = two long values, first is numerator, second is denominator
                    if (iNumValues == 1) {
                        return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset + 4, bBigEnd);
                    } else {
                        var aVals = [];
                        for (var n = 0; n < iNumValues; n++) {
                            aVals[n] = oFile.getLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getLongAt(iValueOffset + 4 + 8 * n, bBigEnd);
                        }
                        return aVals;
                    }
                    break;
                case 9: // slong, 32 bit signed int
                    if (iNumValues == 1) {
                        return oFile.getSLongAt(iEntryOffset + 8, bBigEnd);
                    } else {
                        var aVals = [];
                        for (var n = 0; n < iNumValues; n++) {
                            aVals[n] = oFile.getSLongAt(iValueOffset + 4 * n, bBigEnd);
                        }
                        return aVals;
                    }
                    break;
                case 10: // signed rational, two slongs, first is numerator, second is denominator
                    if (iNumValues == 1) {
                        return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset + 4, bBigEnd);
                    } else {
                        var aVals = [];
                        for (var n = 0; n < iNumValues; n++) {
                            aVals[n] = oFile.getSLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getSLongAt(iValueOffset + 4 + 8 * n, bBigEnd);
                        }
                        return aVals;
                    }
                    break;
            }
        }


        function readEXIFData(oFile, iStart, iLength) {
            if (oFile.getStringAt(iStart, 4) != "Exif") {
                if (bDebug) console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4));
                return false;
            }

            var bBigEnd;

            var iTIFFOffset = iStart + 6;

            // test for TIFF validity and endianness
            if (oFile.getShortAt(iTIFFOffset) == 0x4949) {
                bBigEnd = false;
            } else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) {
                bBigEnd = true;
            } else {
                if (bDebug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
                return false;
            }

            if (oFile.getShortAt(iTIFFOffset + 2, bBigEnd) != 0x002A) {
                if (bDebug) console.log("Not valid TIFF data! (no 0x002A)");
                return false;
            }

            if (oFile.getLongAt(iTIFFOffset + 4, bBigEnd) != 0x00000008) {
                if (bDebug) console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset + 4, bBigEnd));
                return false;
            }

            var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset + 8, EXIF.TiffTags, bBigEnd);

            if (oTags.ExifIFDPointer) {
                var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd);
                for (var strTag in oEXIFTags) {
                    switch (strTag) {
                        case "LightSource" :
                        case "Flash" :
                        case "MeteringMode" :
                        case "ExposureProgram" :
                        case "SensingMethod" :
                        case "SceneCaptureType" :
                        case "SceneType" :
                        case "CustomRendered" :
                        case "WhiteBalance" :
                        case "GainControl" :
                        case "Contrast" :
                        case "Saturation" :
                        case "Sharpness" :
                        case "SubjectDistanceRange" :
                        case "FileSource" :
                            oEXIFTags[strTag] = EXIF.StringValues[strTag][oEXIFTags[strTag]];
                            break;

                        case "ExifVersion" :
                        case "FlashpixVersion" :
                            oEXIFTags[strTag] = String.fromCharCode(oEXIFTags[strTag][0], oEXIFTags[strTag][1], oEXIFTags[strTag][2], oEXIFTags[strTag][3]);
                            break;

                        case "ComponentsConfiguration" :
                            oEXIFTags[strTag] =
                                EXIF.StringValues.Components[oEXIFTags[strTag][0]]
                                    + EXIF.StringValues.Components[oEXIFTags[strTag][1]]
                                    + EXIF.StringValues.Components[oEXIFTags[strTag][2]]
                                    + EXIF.StringValues.Components[oEXIFTags[strTag][3]];
                            break;
                    }
                    oTags[strTag] = oEXIFTags[strTag];
                }
            }

            if (oTags.GPSInfoIFDPointer) {
                var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd);
                for (var strTag in oGPSTags) {
                    switch (strTag) {
                        case "GPSVersionID" :
                            oGPSTags[strTag] = oGPSTags[strTag][0]
                                + "." + oGPSTags[strTag][1]
                                + "." + oGPSTags[strTag][2]
                                + "." + oGPSTags[strTag][3];
                            break;
                    }
                    oTags[strTag] = oGPSTags[strTag];
                }
            }

            return oTags;
        }


        EXIF.getData = function (oImg, fncCallback) {
            if (!oImg.complete) return false;
            if (!imageHasData(oImg)) {
                getImageData(oImg, fncCallback);
            } else {
                if (fncCallback) fncCallback();
            }
            return true;
        }

        EXIF.getTag = function (oImg, strTag) {
            if (!imageHasData(oImg)) return;
            return oImg.exifdata[strTag];
        }

        EXIF.getAllTags = function (oImg) {
            if (!imageHasData(oImg)) return {};
            var oData = oImg.exifdata;
            var oAllTags = {};
            for (var a in oData) {
                if (oData.hasOwnProperty(a)) {
                    oAllTags[a] = oData[a];
                }
            }
            return oAllTags;
        }

        EXIF.pretty = function (oImg) {
            if (!imageHasData(oImg)) return "";
            var oData = oImg.exifdata;
            var strPretty = "";
            for (var a in oData) {
                if (oData.hasOwnProperty(a)) {
                    if (typeof oData[a] == "object") {
                        strPretty += a + " : [" + oData[a].length + " values]\r\n";
                    } else {
                        strPretty += a + " : " + oData[a] + "\r\n";
                    }
                }
            }
            return strPretty;
        }

        EXIF.readFromBinaryFile = function (oFile) {
            return findEXIFinJPEG(oFile);
        }

        function loadAllImages() {
            var aImages = document.getElementsByTagName("img");
            for (var i = 0; i < aImages.length; i++) {
                if (aImages[i].getAttribute("exif") == "true") {
                    if (!aImages[i].complete) {
                        addEvent(aImages[i], "load",
                            function () {
                                EXIF.getData(this);
                            }
                        );
                    } else {
                        EXIF.getData(aImages[i]);
                    }
                }
            }
        }

// automatically load exif data for all images with exif=true when doc is ready
        jQuery(document).ready(loadAllImages);

// load data for images manually
        jQuery.fn.exifLoad = function (fncCallback) {
            return this.each(function () {
                EXIF.getData(this, fncCallback)
            });
        }

        jQuery.fn.exif = function (strTag) {
            var aStrings = [];
            this.each(function () {
                aStrings.push(EXIF.getTag(this, strTag));
            });
            return aStrings;
        }

        jQuery.fn.exifAll = function () {
            var aStrings = [];
            this.each(function () {
                aStrings.push(EXIF.getAllTags(this));
            });
            return aStrings;
        }

        jQuery.fn.exifPretty = function () {
            var aStrings = [];
            this.each(function () {
                aStrings.push(EXIF.pretty(this));
            });
            return aStrings;
        }


    })();


})();